package org.jing.core.socket;

import org.jing.core.logger.JingLogger;
import org.jing.core.util.StringUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.Semaphore;

/**
 * Description: <br>
 *
 * @author bks <br>
 * @since 2020-09-25 <br>
 */
@SuppressWarnings({ "unused" })
public abstract class BaseSocketServer implements Runnable {
    private static final JingLogger LOGGER = JingLogger.getLogger(BaseSocketServer.class);

    private static final HashMap<Class<? extends BaseSocketServer>, HashMap<Thread, HashSet<String>>> THREAD_BATCH_MAP = new HashMap<>();

    private Socket client;

    private Semaphore num;

    private SocketConfiguration config;

    public BaseSocketServer() {}

    public void init(Socket client, Semaphore num, SocketConfiguration config) {
        this.client = client;
        this.num = num;
        this.config = config;
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        if (null != client && !client.isClosed()) {
            try {
                client.close();
            } catch (Throwable ignored) {}
        }
    }

    public SocketConfiguration getConfig() {
        return this.config;
    }

    public boolean addBatch(String batchNo) {
        if (StringUtil.isEmpty(batchNo)) {
            return true;
        }
        synchronized (THREAD_BATCH_MAP) {
            HashMap<Thread, HashSet<String>> threadMap = THREAD_BATCH_MAP.computeIfAbsent(this.getClass(), m$ -> new HashMap<>());
            HashSet<String> batchSet = threadMap.computeIfAbsent(Thread.currentThread(), s$ -> new HashSet<>());
            if (batchSet.contains(batchNo)) {
                LOGGER.imp("batch has already existed in batchSet. [batchNo: {}]", batchNo);
                return false;
            }
            else {
                LOGGER.imp("add batch into batchSet. [batchNo: {}]", batchNo);
                batchSet.add(batchNo);
                return true;
            }
        }
    }

    public void removeBatch(String batchNo) {
        synchronized (THREAD_BATCH_MAP) {
            HashMap<Thread, HashSet<String>> threadMap = THREAD_BATCH_MAP.computeIfAbsent(this.getClass(), m$ -> new HashMap<>());
            HashSet<String> batchSet = threadMap.computeIfAbsent(Thread.currentThread(), s$ -> new HashSet<>());
            LOGGER.imp("remove batch info. [batchNo: {}]", batchNo);
            batchSet.remove(batchNo);
        }
    }

    private void removeAllBatch() {
        synchronized (THREAD_BATCH_MAP) {
            HashMap<Thread, HashSet<String>> threadMap = THREAD_BATCH_MAP.computeIfAbsent(this.getClass(), m$ -> new HashMap<>());
            LOGGER.imp("remove all batch info.");
            threadMap.remove(Thread.currentThread());
        }
    }

    public void run() {
        try (
            InputStream reader = client.getInputStream();
            OutputStream writer = client.getOutputStream()
        ) {
            LOGGER.imp("[{}: {}] start. [ip: {}][port: {}][availablePermits: {}]",
                this.getClass().getSimpleName(), hashCode(),
                client.getInetAddress().getHostAddress(), client.getPort(), num.availablePermits());
            num.acquire();
            operation(reader, writer);
        }
        catch (Throwable t) {
            LOGGER.error("Failed to run Socket Server: {}", t, this,hashCode());
        }
        finally {
            num.release();
            if (null != client && !client.isClosed()) {
                try {
                    client.close();
                    LOGGER.imp("[{}: {}] bye and release...", this.getClass().getSimpleName(), this.hashCode());
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
            removeAllBatch();
        }
    }

    protected abstract void operation(InputStream reader, OutputStream writer) throws Exception;

    public void writeAdvance(String content, String charSet) throws Exception {
        OutputStream writer = client.getOutputStream();
        writer.write(content.getBytes(charSet));
        writer.flush();
    }

    public void closeAdvance() throws Exception {
        if (null != client && !client.isClosed()) {
            client.close();
        }
    }
}
